package com.library.admin.handle;

import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.util.StrUtil;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.baomidou.mybatisplus.core.toolkit.StringUtils;
import com.library.admin.modules.menu.entity.Menu;
import com.library.admin.modules.menu.service.MenuService;
import com.library.admin.modules.role.entity.Role;
import com.library.admin.modules.rolemenu.entity.RoleMenu;
import com.library.admin.modules.rolemenu.service.RoleMenuService;
import com.library.admin.modules.user.bo.CustomUserDetails;
import jakarta.annotation.Resource;
import jakarta.servlet.http.HttpServletRequest;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.security.access.AccessDeniedException;
import org.springframework.security.authentication.AnonymousAuthenticationToken;
import org.springframework.security.authentication.BadCredentialsException;
import org.springframework.security.authorization.AuthorizationDecision;
import org.springframework.security.authorization.AuthorizationManager;
import org.springframework.security.core.Authentication;
import org.springframework.security.web.access.intercept.RequestAuthorizationContext;
import org.springframework.security.web.util.matcher.AntPathRequestMatcher;
import org.springframework.stereotype.Component;

import java.util.List;
import java.util.Set;
import java.util.function.Supplier;
import java.util.stream.Collectors;

/**
 * 自定义授权管理器,可以确定身份验证是否有权访问特定对象。
 *
 * @author: xyh
 * @create: 2023-10-16
 **/
@Component
public class CustomAuthorizationManager implements AuthorizationManager<RequestAuthorizationContext> {

    @Resource
    private RoleMenuService roleMenuService;
    @Resource
    private MenuService menuService;
    @Value("${jwt.tokenHeader}")
    private String tokenHeader;

    @Override
    public AuthorizationDecision check(Supplier<Authentication> authentication, RequestAuthorizationContext requestAuthorizationContext) {
        //获取请求头里面的jwt令牌
        HttpServletRequest httpServletRequest = requestAuthorizationContext.getRequest();
        String token = httpServletRequest.getHeader(tokenHeader);
        if (StrUtil.isEmpty(token)) {
            token = httpServletRequest.getHeader("authorization");
        }
        if (StrUtil.isEmpty(token)) {
            return new AuthorizationDecision(false);
        }
        //表示请求的URL地址和数据库的地址是否匹配上了
        boolean isMatch = false;
        //获取当前请求的URL地址
        HttpServletRequest request = requestAuthorizationContext.getRequest();
        List<Menu> list = menuService.list(new QueryWrapper<>());
        for (Menu m : list) {
            AntPathRequestMatcher antPathRequestMatcher = new AntPathRequestMatcher(m.getUrl());
            if (antPathRequestMatcher.matches(request)) {
                //说明找到了请求的地址了
                //获取当前登录用户的角色
                CustomUserDetails userDetails = (CustomUserDetails) authentication.get().getPrincipal();
                // 获取用户相关角色id信息
                List<Integer> roleIdList = StrUtil.splitTrim(userDetails.getRoles(), StrUtil.COMMA).stream().map(Integer::valueOf).toList();
                // 通过角色id查询相关菜单ids
                Set<Integer> menuIds = roleMenuService.getMenuIdsByRoleIds(roleIdList);
                if (CollUtil.isNotEmpty(menuIds)) {
                    for (Integer menuId : menuIds) {
                        if (menuId.equals(m.getId())) {
                            //说明当前登录用户具备当前请求所需要的菜单
                            isMatch = true;
                            return new AuthorizationDecision(true);
                        }
                    }
                }
            }
        }
        if (!isMatch) {
            //说明请求的 URL 地址和数据库的地址没有匹配上，但当前用户是匿名用户，这表示用户尚未进行身份验证，通常是未登录的用户。
            if (authentication.get() instanceof AnonymousAuthenticationToken) {
                return new AuthorizationDecision(false);
            } else {
                //说明用户已经认证了，但是没有访问该接口的权限
                throw new AccessDeniedException("没有权限访问！");
            }
        }
        return new AuthorizationDecision(false);
    }
}
